<?php
/**
 * Abivia PHP5 Library
 *
 * @package AP5L
 * @license http://www.gnu.org/licenses/gpl-3.0.html GPLv3
 * @copyright 2008, Alan Langford
 * @version $Id: Directory.php 100 2011-03-21 18:26:21Z alan.langford@abivia.com $
 * @author Alan Langford <alan.langford@abivia.com>
 */

/**
 * Helper functions for file paths.
 *
 * @package AP5L
 */
class AP5L_Filesystem_Directory extends AP5L_Php_InflexibleObject {
    /**
     * Destination directory in file copy operations.
     *
     * @var string
     */
    protected $_dest;

    /**
     * The current operation that update() is receiving events for.
     * @var string
     */
    protected $_oprn;

    /**
     * Clean up a directory by converting backslashes to slashes, removing
     * double-slashes, and ensuring a trailing slash is present.
     *
     * @param string The directory to be cleaned.
     * @return string The clean directory.
     */
    static function clean($dir) {
        $dir = str_replace('\\', '/', $dir);
        $dir = preg_replace('|/+|', '/', $dir);
        if (strlen($dir) && $dir[strlen($dir) - 1] != '/') {
            $dir .= '/';
        }
        return $dir;
    }

    /**
     * Recursively create a directory.
     *
     * @param string The directory to be added.
     * @param int Directory permissions.
     */
    static function create($dir, $mode = 0777) {
        $dir = self::clean($dir);
        $parts = explode('/', $dir);
        $path = '';
        while (!empty($parts)) {
            $sub = array_shift($parts);
            if ($sub == '') {
                continue;
            }
            $path .= $sub . '/';
            if (!is_dir($path)) {
                mkdir($path, $mode);
            }
        }
    }

    /**
     * Recursively copy a directory.
     *
     * @param string The source directory.
     * @param string The target directory.
     * @throws AP5L_Filesystem_Exception on error.
     */
    static function copy($src, $dest) {
        $src = self::clean($src);
        self::create($dest);
        $listener = new AP5L_Filesystem_Directory();
        $listener -> setOperation('updateCopy');
        $listener -> setDestination($dest);
        // Get a private event dispatcher
        $disp = AP5L_Event_Dispatcher::getInstance(__CLASS__);
        $disp -> setOption('queue', false);
        $disp -> listen($listener);
        // Create a directory listing, set the dispatcher
        $listing = new AP5L_FileSystem_Listing();
        $listing -> setDispatcher($disp);
        try {
            // Get the listing
            $listing -> execute($src);
        } catch (Exception $err) {
            // Stop listening
            $disp -> unlisten($listener);
            throw $err;
        }
        // Stop listening
        $disp -> unlisten($listener);
    }

    /**
     * Recursively remove a directory.
     *
     * @param string The directory to be removed.
     * @throws AP5L_Filesystem_Exception on error.
     */
    static function delete($dir) {
        $dir = self::clean($dir);
        $listener = new AP5L_Filesystem_Directory();
        $listener -> setOperation('updateDelete');
        // Get a private event dispatcher
        $disp = AP5L_Event_Dispatcher::getInstance(__CLASS__);
        $disp -> setOption('queue', false);
        $disp -> listen($listener);
        // Create a directory listing, set the dispatcher
        $listing = new AP5L_FileSystem_Listing();
        $listing -> setDispatcher($disp);
        try {
            // Get the listing
            $listing -> execute($dir);
        } catch (Exception $err) {
            // Stop listening
            $disp -> unlisten($listener);
            throw $err;
        }
        // Stop listening
        $disp -> unlisten($listener);
    }

    /**
     * Set a destination directory.
     *
     * @param string The target diretory
     */
    function setDestination($dest) {
        $this -> _dest = self::clean($dest);
    }

    /**
     * Set the current operation.
     *
     * @param string The operation name.
     */
    function setOperation($oprn) {
        $this -> _oprn = $oprn;
    }

    /**
     * Receive a file event.
     *
     * This method uses events from the directory walk.
     *
     * @param AP5L_Event_Notification The event object.
     */
    function update(AP5L_Event_Notification $subject) {
        $method = $this -> _oprn;
        $this -> $method($subject);
    }

    /**
     * Receive a file event.
     *
     * This method uses events from the directory walk to copy files.
     *
     * @param AP5L_Event_Notification The event object.
     */
    function updateCopy(AP5L_Event_Notification $subject) {
        switch ($subject -> getName()) {
            case AP5L_Filesystem_Listing::EVENT_ADD_FILE: {
                $info = $subject -> getInfo();
                copy($info['fullname'], $this -> _dest . $info['relname']);
            }
            break;

            case AP5L_Filesystem_Listing::EVENT_LAST_DIR: {
                $info = $subject -> getInfo();
                // This will throw warnings on permissions errors
                @mkdir($this -> _dest . $info['relname']);
            }
            break;

        }
    }

    /**
     * Receive a file event.
     *
     * This method uses events from the directory walk to delete files.
     *
     * @param AP5L_Event_Notification The event object.
     */
    function updateDelete(AP5L_Event_Notification $subject) {
        switch ($subject -> getName()) {
            case AP5L_Filesystem_Listing::EVENT_ADD_FILE: {
                $info = $subject -> getInfo();
                unlink($info['fullname']);
            }
            break;

            case AP5L_Filesystem_Listing::EVENT_LAST_DIR: {
                $info = $subject -> getInfo();
                // This will throw warnings on permissions errors
                @rmdir($info['fullname']);
            }
            break;

        }
    }

}
